Hello!大家好!剛剛差一點點點就睡著了,真是完全都不能懈怠下來,不然我的隊友會殺了我XD,今天要說的東西主要是受控組件(Controlled Component)與不受控組件(Uncontrolled Component),這種組件到底是什麼,讓我們往下看!Check it out!
對,既然標題提到了,我們就不能忽略他對吧?其實受控組件和不受控組件要從表單開始說起,以下先用react
做一個非常非常簡單的Form
表單組件:
class EasyForm extends React.Component {
render() {
return (
<form>
<label>姓名:</label>
<input id="name" name="name" />
<br/>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
點擊提交按鈕submit
後就會送出表單,submit
事件會把表單內的input
的name
和值帶到網址上:
input
的value
綁定state
而大家應該都還記得組件有內部狀態state
這個屬性,所以我們可以將state
的屬性值設定在input
的value
上面,讓state
的值會隨著input
內的值變動!例子如下:
class EasyForm extends React.Component{
constructor(props){
super(props)
//在組件內的state設定一個nameVal屬性,並設值為“預設姓名”
this.state = {name : '預設姓名'}
}
render() {
return (
<form>
<label>姓名:</label>
/*在這裡將state的值設定給name的value*/
<input id="name" name="name" value={this.state.name} />
<br/>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
結果應該不會有太大的問題,就是input
出現我們設定的預設值this.state.name
了:
state
但是!沒看到的問題才是問題!當我們試著要在input
中輸入資料時,卻發現輸入不了啊!讓我們仔細想想原因,現在的input
中的value
是this.state.name
,所以當我要改input
的value
就會動到this.state.name
,有發現了嗎?記得之前曾經說過,在組件中要改變state
只有一個方法!就是setState()
,所以我們趕緊把onChange
加進input
中,讓value
發生改變的時候,可以同時更改state
:
class EasyForm extends React.Component{
constructor(props){
super(props)
this.state = {name : ''}
//設定該function的this為class本身
this.changeState = this.changeState.bind(this)
}
//傳入event要取觸發事件的元件
changeState(event){
//使用setState將值寫到nameVal中
this.setState({name:event.target.value})
console.log('改了改了')
}
render() {
return (
<form>
<label>姓名:</label>
<input type="text" id="name" name="name"
value={this.state.name}
{/*設定onchange事件*/}
onChange={this.changeState} />
<br/>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
如我們所願,加入onChange
讓他同步用setState
更改state
,就可以自由輸入了!
state
取得輸入資料那既然我們可以用state.name
賦予預設值,那能不能從state.name
中把目前使用者輸入的值給取出來?可以的!讓我們為form
添加onsubmit
的function
,看看是否能取到正確的值吧!以下例子:
class EasyForm extends React.Component{
constructor(props){
super(props)
this.state = {name : ''}
this.changeState = this.changeState.bind(this)
this.submitForm = this.submitForm.bind(this)
}
changeState(event){
this.setState({name:event.target.value})
console.log('改了改了')
}
//新增一個submit的function
submitForm(event){
console.log(`現在輸入的名字是:${this.state.name}`)
event.preventDefault()
}
render() {
return (
/*幫form表單新增一個onSubmit事件,讓submit的時候執行*/
<form onSubmit={this.submitForm}>
<label>姓名:</label>
<input type="text" id="name" name="name"
value={this.state.name}
onChange={this.changeState} />
<br/>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
在送出submit
的時候,執行了submitForm
在他裡面有一個event.preventDefault()
是為了防止submit
送出整個網頁,所以用event.preventDefault()
來取消submit
的預設功能(詳細用法可以看這裡),結果如下,果然能夠透過state
取得input
的值:
對吧!就是這麼簡單,因為我們的資料傳輸是雙向的,可以從組件內部的state
綁定value
值,當value
改變的時候也可以同時更改組件的state
,當然啊!這個是input
,像其他的還有textarea
、select
或checkbox
都可以被受控!以下來簡單說明幾種受控組件吧!
textarea
和select
的用法class EasyForm extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
introduction: '',
gender: 'M'
}
this.changeState = this.changeState.bind(this)
this.submitForm = this.submitForm.bind(this)
}
changeState(event) {
/*因為所有的組件改變時都會呼叫這個function
所以這裡就不能像一開始一樣寫死的*/
//首先要去抓目前發生改變的組件的name
let changeName = event.target.name
//再把他目前的value拿去更改state
this.setState({ [changeName]: event.target.value })
}
submitForm(event) {
console.log(`現在輸入的名字是:${this.state.name}`)
console.log(`現在選擇的性別是:${(this.state.gender == 'M')?'男':'女'}`)
console.log(`現在輸入的介紹內容是:${this.state.introduction}`)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.submitForm}>
<div>
<label>姓名:</label>
<input type="text" id="name" name="name"
value={this.state.name}
onChange={this.changeState} />
</div>
{/*需注意的是,textarea和select也是使用value屬性來綁定state*/}
<div>
<label>性別:</label>
<select id="gender" name="gender"
value={this.state.gender}
onChange={this.changeState} >
<option value="M">男</option>
<option value="W">女</option>
</select>
</div>
<div>
<label>自我介紹:</label><br />
<textarea id="introduction" name="introduction"
value={this.state.introduction}
onChange={this.changeState}></textarea>
<br />
</div>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
上面的例子會比較不理解的地方應該是:
changeState(event) {
let changeName = event.target.name
this.setState({ [changeName]: event.target.value })
}
可以寫像上方那樣是因為我們綁定的state
值剛好對應到每個組件的name
屬性,所以才可以直接抓組件name
的屬性來更改state
中的資料,至於加上中括號是為了讓JavaScript
知道那是變數,而不會把它當成changeName
來處理。
結果就會有個擁有雙向綁定的簡易表單:
checkbox
的用法checkbox
的用法比較特別,因為他是多選的,所以就無法只靠設定一個value
來達成目的,先假設我們有幾個選項,並知道該選項是否被勾選,再用迴圈將所有的checkbox
列出來:
class EasyForm extends React.Component {
constructor(props) {
super(props)
this.submitForm = this.submitForm.bind(this)
this.changeState = this.changeState.bind(this)
//這是待辦事項的清單,id是唯一值、listName為事項、check為是否完成
this.state = {lists :
[{id:'01',listName:'寫文章',check:false},
{id:'02',listName:'打程式',check:false},
{id:'03',listName:'耍廢',check:true}]}
}
//傳進index是為了知道目前點選的事項是在陣列中的哪個位置
changeState(index){
//修改時先將this.state.lists指定給一個變數
let arrLists = this.state.lists
//確認清單中的該事項目前狀態是不是已完成
if(arrLists[index].check)
//原本是true的話這時候會變false
arrLists[index].check = false
else
//原本是false的話這時候會變true
arrLists[index].check = true
//改完後用setState將lists重新設定為arrLists
this.setState({lists:arrLists})
}
submitForm(event) {
let status = "目前做了:"
//將陣列中check為true的事項都列出來,代表完成
this.state.lists.map((list)=>{(list.check) ? status += `${list.listName} `:''})
console.log(status)
event.preventDefault()
}
render() {
//使用map跑迴圈,將結果給lists,map的第二個參數index為目前是第幾個索引
let lists = this.state.lists.map((list,index)=>(
/*既然使用迴圈,就要設key對吧!*/
<div key={list.id}>
{/*這裡將checked屬性設定成清單中的check,true就打勾、false就沒勾
onChange中代入第二個參數index,是為了辨別變動的是第幾個索引的事項
最後因為是迴圈,所以要記得設key對吧!*/}
<input type="checkbox"
checked={list.check}
onChange={this.changeState.bind(this,index)}
key={list.id} />
<label>{list.listName}</label>
</div>
));
return (
<form onSubmit={this.submitForm}>
<div>
<label>每日待辦清單:</label>
{/*直接將剛剛跑完迴圈的變數放進來*/}
{lists}
</div>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
結果如下:
上面的程式碼看起來會有點複雜,但是其實只是多了迴圈的用法,還有控制checkBox
的屬性值變成true
或false
,並設定在checked
而不是value
上,其他都還是一樣,只要嘗試做一次就很容易理解,那以下附上Github連結讓各位參考
對!到最後總是要提一下這個,既然受控組件是可以用state
去設定組件的value
,那沒辦法這樣做的就例如file
,因為必須由使用者選擇檔案,再去取值上傳,這種情況下我們沒辦法去設定他的值是什麼,所以被稱為不受控組件。
當我們要去取得不受控組件的值時,當然沒辦法用state
取得,針對這種狀況可以利用React.createRef()
來處理,這是初次見面的函式,他能夠讓你在不使用選擇器的狀態下直接操作組件內的DOM
,以下來看看他的厲害吧!
class EasyForm extends React.Component {
constructor(props) {
super(props)
this.submitForm = this.submitForm.bind(this)
//使用React.createRef()建立一個物件給filebox
this.filebox = React.createRef()
}
submitForm(event) {
/*在function內就可以直接取用
React.createRef()建立的this.filebox來取得對應設定ref的組件*/
console.log(`選擇檔案為:${this.filebox.current.files[0].name}`)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.submitForm}>
<div>
<label>上傳檔案:</label>
<input type="file"
/*這裡將用React.createRef的filebox指定給該組件的ref屬性
讓class內的function可以依照ref取得組件*/
ref={this.filebox} />
</div>
<input type="submit" value="送出表單" />
</form>
)
}
}
ReactDOM.render(<EasyForm />, document.getElementById('root'))
結果如下:
本篇稍微有點長,不過幾乎都是在講同一個概念,只是換成各種表單的用法XD,而且最後出現的React.createRef()
表現也非常驚艷(對我來說啦XD),雖然大部分時候都直接用state
啦!但是多知道一種用法就多一項武器嘛!
最後感謝各位大大的觀看,如果文章中有任何問題,還麻煩留言告訴我,小弟會在盡快修正或補充文章的!謝謝大家
參考文章: